import { ComponentFixture, TestBed } from "@angular/core/testing";
import { FitnessTrendGraphComponent } from "./fitness-trend-graph.component";
import { FitnessService } from "../shared/services/fitness.service";
import { ActivityService } from "../../shared/services/activity/activity.service";
import ACTIVITIES_FIXTURES from "../../../shared-fixtures/activities-2015.fixture.json";
import moment, { Moment } from "moment";
import { CoreModule } from "../../core/core.module";
import { SharedModule } from "../../shared/shared.module";
import { DayFitnessTrendModel } from "../shared/models/day-fitness-trend.model";
import _ from "lodash";
import { PeriodModel } from "../shared/models/period.model";
import { FitnessTrendModule } from "../fitness-trend.module";
import { HeartRateImpulseMode } from "../shared/enums/heart-rate-impulse-mode.enum";
import { UserSettingsService } from "../../shared/services/user-settings/user-settings.service";
import { DataStore } from "../../shared/data-store/data-store";
import { TestingDataStore } from "../../shared/data-store/testing-datastore.service";
import { WindowService } from "../../shared/services/window/window.service";
import { TargetModule } from "../../shared/modules/target/desktop-target.module";
import { IPC_TUNNEL_SERVICE } from "../../desktop/ipc/ipc-tunnel-service.token";
import { IpcRendererTunnelServiceMock } from "../../desktop/ipc/ipc-renderer-tunnel-service.mock";
import { UserSettings } from "@elevate/shared/models/user-settings/user-settings.namespace";
import DesktopUserSettings = UserSettings.DesktopUserSettings;

describe("FitnessTrendGraphComponent", () => {
  let userSettingsService: UserSettingsService;
  let activityService: ActivityService;
  let fitnessService: FitnessService;
  let windowService: WindowService;
  let component: FitnessTrendGraphComponent;
  let fixture: ComponentFixture<FitnessTrendGraphComponent>;
  let todayMoment: Moment;
  let updateGraphSpy: jasmine.Spy;

  const todayDate = "2015-12-01 12:00";
  const momentDatePattern = "YYYY-MM-DD hh:mm";

  let FITNESS_TREND: DayFitnessTrendModel[] = null;

  beforeEach(done => {
    TestBed.configureTestingModule({
      imports: [CoreModule, SharedModule, TargetModule, FitnessTrendModule],
      providers: [
        { provide: DataStore, useClass: TestingDataStore },
        { provide: IPC_TUNNEL_SERVICE, useClass: IpcRendererTunnelServiceMock }
      ]
    }).compileComponents();

    // Retrieve injected service
    userSettingsService = TestBed.inject(UserSettingsService);
    activityService = TestBed.inject(ActivityService);
    fitnessService = TestBed.inject(FitnessService);
    windowService = TestBed.inject(WindowService);

    // Mocking
    spyOn(activityService, "fetch").and.returnValue(Promise.resolve(_.cloneDeep(ACTIVITIES_FIXTURES)));
    spyOn(userSettingsService, "fetch").and.returnValue(
      Promise.resolve(_.cloneDeep(DesktopUserSettings.DEFAULT_MODEL))
    );

    todayMoment = moment("2015-12-01 12:00", "YYYY-MM-DD hh:mm");
    spyOn(fitnessService, "getTodayMoment").and.returnValue(todayMoment);
    spyOn(windowService, "isScreenMediaActive").and.returnValue(false);

    const fitnessTrendConfigModel = {
      heartRateImpulseMode: HeartRateImpulseMode.HRSS,
      initializedFitnessTrendModel: {
        atl: null,
        ctl: null
      },
      allowEstimatedPowerStressScore: false,
      allowEstimatedRunningStressScore: false,
      ignoreBeforeDate: null,
      ignoreActivityNamePatterns: null
    };

    const powerMeterEnable = true;
    const swimEnable = true;

    const promise: Promise<DayFitnessTrendModel[]> = fitnessService.computeTrend(
      fitnessTrendConfigModel,
      powerMeterEnable,
      swimEnable
    );
    promise.then((fitnessTrend: DayFitnessTrendModel[]) => {
      FITNESS_TREND = fitnessTrend;
      done();
    });
  });

  beforeEach(done => {
    fixture = TestBed.createComponent(FitnessTrendGraphComponent);

    component = fixture.componentInstance;
    component.isTrainingZonesEnabled = true;

    component.fitnessTrend = FITNESS_TREND;
    component.dateMin = moment(_.first(FITNESS_TREND).date).startOf("day").toDate();
    component.dateMax = moment(_.last(FITNESS_TREND).date).startOf("day").toDate();

    component.periodViewed = {
      from: component.dateMin,
      to: component.dateMax
    };

    spyOn(component, "getTodayViewedDay").and.returnValue(component.getDayFitnessTrendFromDate(todayMoment.toDate()));
    updateGraphSpy = spyOn(component, "updateGraph").and.stub(); // Do not try to draw the graph

    fixture.detectChanges();

    done();
  });

  it("should create", done => {
    expect(component).toBeTruthy();
    done();
  });

  it("should allow forward period viewed", done => {
    // Given
    component.dateMin = moment("2015-01-01", "YYYY-MM-DD").toDate();
    component.dateMax = moment("2015-01-31", "YYYY-MM-DD").toDate();

    const periodFrom = "2015-01-06";
    const periodTo = "2015-01-16";

    component.periodViewed = {
      from: moment(periodFrom, "YYYY-MM-DD").toDate(),
      to: moment(periodTo, "YYYY-MM-DD").toDate()
    };
    const expectedPeriodFrom = moment("2015-01-20", "YYYY-MM-DD").toDate();
    const expectedPeriodTo = moment("2015-01-30", "YYYY-MM-DD").toDate();

    // When
    component.onPeriodViewedForward();

    // Then
    expect(component.periodViewed.from.getTime()).toBe(expectedPeriodFrom.getTime());
    expect(component.periodViewed.to.getTime()).toBe(expectedPeriodTo.getTime());
    expect(updateGraphSpy).toHaveBeenCalledTimes(2);
    done();
  });

  it("should NOT forward period viewed", done => {
    // Given
    component.dateMin = moment("2015-01-01", "YYYY-MM-DD").toDate();
    component.dateMax = moment("2015-01-31", "YYYY-MM-DD").toDate();

    const periodFrom = "2015-01-10";
    const periodTo = "2015-01-20";
    component.periodViewed = {
      from: moment(periodFrom, "YYYY-MM-DD").toDate(),
      to: moment(periodTo, "YYYY-MM-DD").toDate()
    };

    const expectedPeriodFrom = moment(periodFrom, "YYYY-MM-DD").toDate();
    const expectedPeriodTo = moment(periodTo, "YYYY-MM-DD").toDate();

    // When
    component.onPeriodViewedForward();

    // Then
    expect(component.periodViewed.from.getTime()).toBe(expectedPeriodFrom.getTime());
    expect(component.periodViewed.to.getTime()).toBe(expectedPeriodTo.getTime());
    expect(updateGraphSpy).toHaveBeenCalledTimes(1);
    done();
  });

  it("should allow backward period viewed", done => {
    // Given
    component.dateMin = moment("2015-01-01", "YYYY-MM-DD").toDate();
    component.dateMax = moment("2015-01-31", "YYYY-MM-DD").toDate();

    const periodFrom = "2015-01-20";
    const periodTo = "2015-01-30";

    component.periodViewed = {
      from: moment(periodFrom, "YYYY-MM-DD").toDate(),
      to: moment(periodTo, "YYYY-MM-DD").toDate()
    };
    const expectedPeriodFrom = moment("2015-01-6", "YYYY-MM-DD").toDate();
    const expectedPeriodTo = moment("2015-01-16", "YYYY-MM-DD").toDate();

    // When
    component.onPeriodViewedBackward();

    // Then
    expect(component.periodViewed.from.toDateString()).toBe(expectedPeriodFrom.toDateString());
    expect(component.periodViewed.to.toDateString()).toBe(expectedPeriodTo.toDateString());
    expect(updateGraphSpy).toHaveBeenCalledTimes(2);
    done();
  });

  it("should NOT backward period viewed", done => {
    // Given
    component.dateMin = moment("2015-01-01", "YYYY-MM-DD").toDate();
    component.dateMax = moment("2015-01-31", "YYYY-MM-DD").toDate();

    const periodFrom = "2015-01-10";
    const periodTo = "2015-01-20";
    component.periodViewed = {
      from: moment(periodFrom, "YYYY-MM-DD").toDate(),
      to: moment(periodTo, "YYYY-MM-DD").toDate()
    };

    const expectedPeriodFrom = moment(periodFrom, "YYYY-MM-DD").toDate();
    const expectedPeriodTo = moment(periodTo, "YYYY-MM-DD").toDate();

    // When
    component.onPeriodViewedBackward();

    // Then
    expect(component.periodViewed.from.getTime()).toBe(expectedPeriodFrom.getTime());
    expect(component.periodViewed.to.getTime()).toBe(expectedPeriodTo.getTime());
    expect(updateGraphSpy).toHaveBeenCalledTimes(1);
    done();
  });

  it("should allow zoom in period viewed", done => {
    // Given
    component.dateMin = moment("2015-01-01", "YYYY-MM-DD").toDate();
    component.dateMax = moment("2015-12-31", "YYYY-MM-DD").toDate();

    const periodFrom = "2015-06-01";
    const periodTo = "2015-12-31";

    component.periodViewed = {
      from: moment(periodFrom, "YYYY-MM-DD").toDate(),
      to: moment(periodTo, "YYYY-MM-DD").toDate()
    };
    const expectedPeriodFrom = moment("2015-06-15", "YYYY-MM-DD").toDate();
    const expectedPeriodTo = moment("2015-12-31", "YYYY-MM-DD").toDate();

    // When
    component.onPeriodViewedZoomIn();

    // Then
    expect(component.periodViewed.from.getTime()).toBe(expectedPeriodFrom.getTime());
    expect(component.periodViewed.to.getTime()).toBe(expectedPeriodTo.getTime());
    expect(updateGraphSpy).toHaveBeenCalledTimes(2);
    done();
  });

  it("should NOT allow zoom in period viewed", done => {
    // Given
    component.dateMin = moment("2015-01-01", "YYYY-MM-DD").toDate();
    component.dateMax = moment("2015-12-31", "YYYY-MM-DD").toDate();

    const periodFrom = "2015-06-20";
    const periodTo = "2015-06-30";

    component.periodViewed = {
      from: moment(periodFrom, "YYYY-MM-DD").toDate(),
      to: moment(periodTo, "YYYY-MM-DD").toDate()
    };
    const expectedPeriodFrom = moment(periodFrom, "YYYY-MM-DD").toDate();
    const expectedPeriodTo = moment(periodTo, "YYYY-MM-DD").toDate();

    // When
    component.onPeriodViewedZoomIn();

    // Then
    expect(component.periodViewed.from.getTime()).toBe(expectedPeriodFrom.getTime());
    expect(component.periodViewed.to.getTime()).toBe(expectedPeriodTo.getTime());
    expect(updateGraphSpy).toHaveBeenCalledTimes(1);
    done();
  });

  it("should allow zoom out period viewed", done => {
    // Given
    component.dateMin = moment("2015-01-01", "YYYY-MM-DD").toDate();
    component.dateMax = moment("2015-12-31", "YYYY-MM-DD").toDate();

    const periodFrom = "2015-05-15";
    const periodTo = "2015-12-31";

    component.periodViewed = {
      from: moment(periodFrom, "YYYY-MM-DD").toDate(),
      to: moment(periodTo, "YYYY-MM-DD").toDate()
    };
    const expectedPeriodFrom = moment("2015-05-01", "YYYY-MM-DD").toDate();
    const expectedPeriodTo = moment("2015-12-31", "YYYY-MM-DD").toDate();

    // When
    component.onPeriodViewedZoomOut();

    // Then
    expect(component.periodViewed.from.getTime()).toBe(expectedPeriodFrom.getTime());
    expect(component.periodViewed.to.getTime()).toBe(expectedPeriodTo.getTime());
    expect(updateGraphSpy).toHaveBeenCalledTimes(2);
    done();
  });

  it("should NOT allow zoom out period viewed", done => {
    // Given
    component.dateMin = moment("2015-01-01", "YYYY-MM-DD").toDate();
    component.dateMax = moment("2015-12-31", "YYYY-MM-DD").toDate();

    const periodFrom = "2015-01-10";
    const periodTo = "2015-10-31";

    component.periodViewed = {
      from: moment(periodFrom, "YYYY-MM-DD").toDate(),
      to: moment(periodTo, "YYYY-MM-DD").toDate()
    };
    const expectedPeriodFrom = moment(periodFrom, "YYYY-MM-DD").toDate();
    const expectedPeriodTo = moment(periodTo, "YYYY-MM-DD").toDate();

    // When
    component.onPeriodViewedZoomOut();

    // Then
    expect(component.periodViewed.from.getTime()).toBe(expectedPeriodFrom.getTime());
    expect(component.periodViewed.to.getTime()).toBe(expectedPeriodTo.getTime());
    expect(updateGraphSpy).toHaveBeenCalledTimes(1);
    done();
  });

  it("should convert -7 days date based period 'from/to' to 'start/end' fitness trends indexes", done => {
    // Given
    const period: PeriodModel = {
      from: moment(todayDate, momentDatePattern).subtract(7, "days").toDate(), // Nov 24 2015
      to: null // Indicate we use "Last period of TIME"
    };

    // When
    const indexes: { start: number; end: number } = component.indexesOf(period);

    // Then
    expect(indexes).not.toBeNull();
    expect(indexes.start).toEqual(324); // Should be Nov 24 2015
    expect(indexes.end).toEqual(345); // Last preview day index
    done();
  });

  it("should convert -6 weeks date based period 'from/to' to 'start/end' fitness trends indexes", done => {
    // Given
    const period: PeriodModel = {
      from: moment(todayDate, momentDatePattern).subtract(6, "weeks").toDate(), // (= Oct 20 2015)
      to: null // Indicate we use "Last period of TIME"
    };

    // When
    const indexes: { start: number; end: number } = component.indexesOf(period);

    // Then
    expect(indexes.start).toEqual(289); // Should be Oct 20 2015 index
    expect(indexes.end).toEqual(345); // Last preview day index
    done();
  });

  it("should convert date based period 'from/to' to 'start/end' fitness trends indexes", done => {
    // Given
    const period: PeriodModel = {
      from: moment("2015-07-01", DayFitnessTrendModel.DATE_FORMAT).startOf("day").toDate(),
      to: moment("2015-09-30", DayFitnessTrendModel.DATE_FORMAT).startOf("day").toDate()
    };

    // When
    const indexes: { start: number; end: number } = component.indexesOf(period);

    // Then
    expect(indexes).not.toBeNull();
    expect(indexes.start).toEqual(178);
    expect(indexes.end).toEqual(269);
    done();
  });

  it("should failed when find indexes of 'from > to' date", done => {
    // Given
    const period: PeriodModel = {
      from: moment("2015-06-01", DayFitnessTrendModel.DATE_FORMAT).toDate(),
      to: moment("2015-05-01", DayFitnessTrendModel.DATE_FORMAT).toDate()
    };

    // When
    let error = null;
    try {
      component.indexesOf(period);
    } catch (e) {
      error = e;
    }

    // Then
    expect(error).not.toBeNull();
    expect(error).toBe("FROM cannot be upper than TO date");
    done();
  });

  it("should provide 'start' index of the first known activity when FROM don't matches athlete activities", done => {
    // Given
    const period: PeriodModel = {
      from: moment("2014-06-01", DayFitnessTrendModel.DATE_FORMAT).startOf("day").toDate(), // Too old date !
      to: moment("2015-09-30", DayFitnessTrendModel.DATE_FORMAT).startOf("day").toDate()
    };

    // When
    const indexes: { start: number; end: number } = component.indexesOf(period);

    // Then
    expect(indexes).not.toBeNull();
    expect(indexes.start).toEqual(0);
    expect(indexes.end).toEqual(269);

    done();
  });

  it("should failed when find index of TO which do not exists ", done => {
    // Given
    const period: PeriodModel = {
      from: moment("2015-06-01", DayFitnessTrendModel.DATE_FORMAT).toDate(),
      to: moment("2018-05-01", DayFitnessTrendModel.DATE_FORMAT).toDate() // Fake
    };

    // When
    let error = null;
    try {
      component.indexesOf(period);
    } catch (e) {
      error = e;
    }

    // Then
    expect(error).not.toBeNull();
    expect(error).toBe("No end activity index found for this TO date");
    done();
  });
});
